/* Classic Ladder Project */
/* Copyright (C) 2001-2012 Marc Le Douarain */
/* http://www.multimania.com/mavati/classicladder */
/* http://www.sourceforge.net/projects/classicladder */
/* February 2001 */
/* ------------------------------ */
/* The main for Linux Application */
/* ------------------------------ */
/* This library is free software; you can redistribute it and/or */
/* modify it under the terms of the GNU Lesser General Public */
/* License as published by the Free Software Foundation; either */
/* version 2.1 of the License, or (at your option) any later version. */

/* This library is distributed in the hope that it will be useful, */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU */
/* Lesser General Public License for more details. */

/* You should have received a copy of the GNU Lesser General Public */
/* License along with this library; if not, write to the Free Software */
/* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

#ifdef MODULE
#include <linux/string.h>
#else
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#endif

#include "classicladder.h"
#include "global.h"
#include "files_project.h"
#include "calc.h"
#include "vars_access.h"
#include "manager.h"
#include "calc_sequential.h"
#include "files_sequential.h"
//#include "config.h"
#include "hardware.h"
#include "socket_server.h"
#include "socket_modbus_master.h"
#include "vars_system.h"
#ifdef COMPLETE_PLC
#include "monitor_threads.h"
#endif

#if !defined( MODULE )
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
#include <signal.h>
#include <getopt.h>
#ifdef __WIN32__
#include <windows.h>
#else
#include <pthread.h>
#include <syslog.h>
#endif
#ifdef __XENO__
#include <sys/mman.h>
#endif
#endif

#ifdef GTK_INTERFACE
#include <gtk/gtk.h>
#include "classicladder_gtk.h"
#include "manager_gtk.h"
#include "menu_and_toolbar_gtk.h"
#endif

#ifdef MAT_CONNECTION
#include "../../lib/plc.h"
#endif

#include "preferences.h"
#include "tasks.h"
#include "monitor_transfer.h"

int cl_remote;
int nogui = 0;
int ModbusServerPort = 9502; // Standard "502" requires root privileges...

#if !defined( MODULE )

char TheProject[400] = "";
//////#ifdef GTK_INTERFACE
//char TheProject[400] = "projects_examples/example.clprj";
//////#else
//////char TheProject[400] = "projects_examples/parallel_port_direct.clprj";
//////#endif

char QuitClassicLadder = FALSE;

#if defined( HARD_LIFE_LED_PORT ) && defined (HARD_LIFE_LED_BIT )
unsigned int LifeLedDatasToWrite = 0;
int LifeCounterPeriod = 0;
#endif

void ClassicLadderEndOfAppli( void )
{
	debug_printf("EndOfAppli function...\n");
	QuitClassicLadder = TRUE;
	StopRunIfRunning( );
	EndTaskWanted( ID_TASK_LOGIC );
	EndTaskWanted( ID_TASK_SCAN_INPUTS );
//done before	StopRunIfRunning( );
#ifdef COMPLETE_PLC
	EndGeneralMonitor( );
#endif
#ifdef MODBUS_IO_MASTER
	CloseSocketModbusMaster( );
#endif
	CloseSocketServer( );
#ifdef GTK_INTERFACE
	SavePreferences( );
#endif
	ClassicLadder_FreeAll( TRUE/*CleanAndRemoveTmpDir*/ );
	EndHardware( );
	EndGeneralMutex( );
	debug_printf("Normal end of appli.\n");
#ifndef __WIN32__
	if ( Preferences.UseSysLog )
		syslog( LOG_USER|LOG_DEBUG, "Normal end of application.\n" );
#endif
}

void HandlerSignalInterrupt( int signal_id )
{
	debug_printf("End of appli asked (signal=%d)\n", signal_id);
#ifndef __WIN32__
	if ( Preferences.UseSysLog )
		syslog( LOG_USER|LOG_DEBUG, "End of application asked with signal=%d.\n",signal_id );
#endif
	// security if ctrl-c loop received ??!!
	if ( !QuitClassicLadder )
		ClassicLadderEndOfAppli( );
	else
		debug_printf("Already under quit...!!!???\n");
	exit( 0 );
}
void HandlerSignalSegfault( int signal_id )
{
	debug_printf("Abnormal end of appli (SEGV)\n");
#ifndef __WIN32__
	if ( Preferences.UseSysLog )
		syslog( LOG_USER|LOG_DEBUG, "Abnormal end of application (SEGV)\n" );
#endif
	ClassicLadderEndOfAppli( );
	exit( 0 );
}

void display_help (void)
{
	printf("Usage: classicladder [OPTIONS] [PATH]\n"
			"Start classicladder PLC with an optional project path e.g. myplc\n"
			"\n"
			"           --help     	        display this help and exit\n"
			"           --version  	        output version information and exit\n"
			"           --nogui             do not create the GUI\n"
			"-p port    --modbus_port=port   port to use for modbus server\n"
//			"-c file    --config=file        read PLC configuration from file\n"
			);
	exit(0);
}

void display_version (void)
{
	printf( CL_PRODUCT_NAME " v" CL_RELEASE_VER_STRING "\n" CL_RELEASE_DATE_STRING "\n"
	       "\n"
               "Copyright (C) " CL_RELEASE_COPYRIGHT_YEARS " Marc Le Douarain\nmarc . le - douarain /At\\ laposte \\DoT/ net\n"
	       "\n"
	       "ClassicLadder comes with NO WARRANTY\n"
	       "to the extent permitted by law.\n"
	       "\n"
	       "You may redistribute copies of ClassicLadder\n"
	       "under the terms of the GNU Lesser General Public Licence.\n"
	       "See the file `lesserGPL.txt' for more information.\n");
	exit(0);
}

void process_options (int argc, char *argv[])
{
	int error = 0;

	for (;;)
	{
		int option_index = 0;
		static const char *short_options = "c:";
		static const struct option long_options[] = {
			{"help", no_argument, 0, 0},
			{"version", no_argument, 0, 0},
			{"nogui", no_argument, 0, 'n'},
//			{"config", required_argument, 0, 'c'},
			{"modbus_port", required_argument, 0, 'p'},
			{0, 0, 0, 0},
		};

		int c = getopt_long(argc, argv, short_options,
				    long_options, &option_index);
		if (c == EOF)
			break;

		switch (c)
		{
			case 0:
				switch (option_index) {
				case 0:
					display_help();
					break;
				case 1:
					display_version();
					break;
				}
				break;
			case 'n':
				nogui = 1;
				break;
/*			case 'c':
	#ifndef RT_SUPPORT
				read_config (optarg);
	#else
				printf("Config file is used by the RT module in RTLinux version !!!\n");
	#endif
				break;*/
			case 'p':
				ModbusServerPort = atoi( optarg );
				break;
			case '?':
				error = 1;
				break;
		}
	}

	if (error)
		display_help ();

	if ((argc - optind) != 0)
		VerifyDirectorySelected( TheProject, argv[optind] );
}


void DoFlipFlopRunStop( void )
{
#ifdef GTK_INTERFACE
	if ( InfosGUI->TargetMonitor.RemoteConnected )
	{
		// Send order to switch run/stop the target !!!
		InfosGUI->TargetMonitor.AskTargetToSetState = (InfosGUI->TargetMonitor.LatestTargetState==STATE_RUN)?STATE_STOP:STATE_RUN;
printf("STATE WANTED FOR TARGET = %s (current=%s)\n", _ConvStateInString(InfosGUI->TargetMonitor.AskTargetToSetState),_ConvStateInString(InfosGUI->TargetMonitor.LatestTargetState) );
		return;
	}
#endif
	if (InfosGene->LadderState==STATE_RUN)
	{
		InfosGene->LadderState = STATE_STOP;
		InfosGene->DurationOfLastScan = -1;
#ifdef GTK_INTERFACE
//		gtk_label_set_text(GTK_LABEL(GTK_BIN(ButtonRunStop)->child),"Run");
		MessageInStatusBar("Stopped program - press run button to continue.");
//		SetToggleMenuForRunStop( TRUE );
		SetGtkMenuStateForRunStopSwitch( FALSE );
#endif
	}
	else
	{
		InfosGene->LadderState = STATE_RUN;
#ifdef GTK_INTERFACE
//		gtk_label_set_text(GTK_LABEL(GTK_BIN(ButtonRunStop)->child),"Stop");
		MessageInStatusBar("Started program - press stop to pause."); 
//		SetToggleMenuForRunStop( FALSE );
		SetGtkMenuStateForRunStopSwitch( TRUE );
#endif
	}
}

void StopRunIfRunning( void )
{
	if (InfosGene->LadderState==STATE_RUN)
	{
		debug_printf("Stopping...");
		InfosGene->LadderStoppedToRunBack = TRUE;
		InfosGene->LadderState = STATE_STOP;
		InfosGene->DurationOfLastScan = -1;
		while( InfosGene->UnderCalculationPleaseWait==TRUE )
		{
			DoPauseMilliSecs( 100 );
		}
		debug_printf("done.\n");
	}
}
void RunBackIfStopped( void )
{
	if ( InfosGene->LadderStoppedToRunBack )
	{
		debug_printf("Start running!\n");
		InfosGene->LadderState = STATE_RUN;
		InfosGene->LadderStoppedToRunBack = FALSE;
	}
}

void DoReset( void )
{
	// reset with monitor only possible on an embedded!
#ifdef GTK_INTERFACE
	if ( InfosGUI->TargetMonitor.RemoteConnected )
	{
		// Send order to switch run/stop the target !!!
		InfosGUI->TargetMonitor.AskTargetToReset = 1;
printf("RESET WANTED OF THE TARGET\n" );
		return;
	}
#endif

	InitVars( TRUE/*DoLogEvents*/ );

//////	int StateBefore = InfosGene->LadderState;
//////	InfosGene->LadderState = STATE_STOP;
//////	// wait, to be sure calcs have ended...
//////	usleep( 100000 );
	StopRunIfRunning( );

//v0.9.4, moved before to log bools reset!!!	InitVars( );
	InitSystemVars( FALSE );
	PrepareAllDatasBeforeRun( );

//////	if ( StateBefore==STATE_RUN )
//////		InfosGene->LadderState = STATE_RUN;
	RunBackIfStopped( );
#ifdef GTK_INTERFACE
	MessageInStatusBar((InfosGene->LadderState==STATE_RUN)?"Reset logic data - Now running.":"Reset logic data done.");
#endif
}

void LifeLed( )
{
#if defined( HARD_LIFE_LED_PORT ) && defined (HARD_LIFE_LED_BIT )
	unsigned int LedMask = (1<<HARD_LIFE_LED_BIT );
	int CounterMax = (InfosGene->LadderState==STATE_RUN && !InfosGene->AskToConfHardInputs)?500:100;
	if ( LifeCounterPeriod>=CounterMax )
	{
		LifeCounterPeriod = 0;
		if( LifeLedDatasToWrite & LedMask )
			LifeLedDatasToWrite = LifeLedDatasToWrite & (~LedMask);
		else
			LifeLedDatasToWrite = LifeLedDatasToWrite | LedMask;
	}
	LifeCounterPeriod = LifeCounterPeriod+InfosGene->GeneralParams.PeriodMilliSecsTaskLogic;
//printf("life led, period=%d(+%d), max=%d, value=%x\n", LifeCounterPeriod, InfosGene->GeneralParams.PeriodMilliSecsTaskLogic, CounterMax, LifeLedDatasToWrite);
#endif
}

void CyclicCalcRefreshOfScanInputsTask( void )
{
	do
	{

		PeriodicTaskCycleStart( ID_TASK_SCAN_INPUTS );
		if ( TaskIsRunning( ID_TASK_SCAN_INPUTS ) )
		{
			
			if (InfosGene->LadderState==STATE_RUN)
			{
#ifdef GTK_INTERFACE
				if ( !GeneralParamsMirror.RealInputsOutputsOnlyOnTarget )
#endif
				{
					// called here, because ioperm seems requiring to be defined in this thread using them.
					if ( InfosGene->AskToConfHardInputs )
					{
						ConfigHardware( 0/*ForOutputs*/ );
						InfosGene->AskToConfHardInputs = FALSE;
					}

					ReadPhysicalInputs();
				}
#ifdef GTK_INTERFACE
				else
				{
					InfosGene->AskToConfHardInputs = FALSE;
				}
#endif
			}
		}
		PeriodicTaskCycleEnd( ID_TASK_SCAN_INPUTS );
	}
	while( TaskIsRunning( ID_TASK_SCAN_INPUTS ) );
	TaskExit( ID_TASK_SCAN_INPUTS );
}
void CyclicCalcRefreshOfLogicTask( void )
{
	ConfigHardwareForLifeLed( );

	do
	{

		PeriodicTaskCycleStart( ID_TASK_LOGIC );
		if ( TaskIsRunning( ID_TASK_LOGIC ) )
		{
			
			LifeLed( );
			
			if ( InfosGene->LadderState==STATE_RUN && !InfosGene->AskToConfHardInputs )
			{
				// called here, because ioperm seems requiring to be defined in this thread using them.
				if ( InfosGene->AskToConfHardOutputs )
				{
					ConfigHardware( 1/*ForOutputs*/ );
					InfosGene->AskToConfHardOutputs = FALSE;
				}

				InfosGene->UnderCalculationPleaseWait = TRUE;
//////				ReadPhysicalInputs();
				CopyInputsStatesToInputsVars( );
				UpdateSystemVars( );
				ClassicLadder_RefreshAllSections();
#ifdef GTK_INTERFACE
				if ( !GeneralParamsMirror.RealInputsOutputsOnlyOnTarget )
#endif
				{
					WritePhysicalOutputs( FALSE/*OnlyForLifeLed*/ );
				}
				InfosGene->UnderCalculationPleaseWait = FALSE;

			}//if (InfosGene->LadderState==STATE_RUN
			else
			{
#ifndef GTK_INTERFACE
				WritePhysicalOutputs( TRUE/*OnlyForLifeLed*/ );
#endif
			}

			SecurityForAbortedTransferToSlave( );
		}
		PeriodicTaskCycleEnd( ID_TASK_LOGIC );
	}
	while( TaskIsRunning( ID_TASK_LOGIC ) );
	TaskExit( ID_TASK_LOGIC );
}

int main( int   argc, char *argv[] )
{
	InitPreferences( );
	LoadPreferences( );
#ifndef __WIN32__
	if ( Preferences.UseSysLog )
	{
		openlog( "ClassicLadder", LOG_CONS|LOG_PERROR, LOG_USER );
		syslog( LOG_USER|LOG_DEBUG, "Starting... (default project=%s)\n", Preferences.DefaultProjectFileNameToLoadAtStartup );
	}
	if ( Preferences.Daemonize )
		daemon( 1, 1 );
#endif
#ifdef __XENO__
	mlockall(MCL_CURRENT | MCL_FUTURE);
#endif

	InitGeneralMutex( );
	InitTasks( );
	InitHardware( );
#ifdef MODBUS_IO_MASTER
	InitModbusMasterBeforeReadConf( );
#endif
	if ( Preferences.DefaultProjectFileNameToLoadAtStartup[ 0 ]!='\0' )
		strcpy( TheProject, Preferences.DefaultProjectFileNameToLoadAtStartup );
	process_options (argc, argv);

	InitGeneralParamsMirror( );
	// here we get the sizes defined in the project...
	// (before doing the allocations !)
	LoadGeneralParamsOnlyFromProject( TheProject );

	if (ClassicLadder_AllocAll())
	{
		// now InfosGene allocated...
		strcpy( InfosGene->CurrentProjectFileName, TheProject );

		InitSocketServer( 0/*UseUdpMode*/, ModbusServerPort/*PortNbr*/ );
#ifdef MODBUS_IO_MASTER
		InitSocketModbusMaster( );
#endif
#ifdef COMPLETE_PLC
		InitGeneralMonitor( /*ListenPortForSlave*/9000, /*InitOnlyTheSlave*/FALSE );
#endif

		signal( SIGINT /*SIGTERM*/, HandlerSignalInterrupt );
		signal( SIGTERM, HandlerSignalInterrupt );
#ifndef NO_SEGV_TRAP
		signal( SIGSEGV, HandlerSignalSegfault );
#endif

printf( "tasks periods (GeneralParams): inputs=%dms , logic=%dms\n", InfosGene->GeneralParams.PeriodMilliSecsTaskScanInputs, InfosGene->GeneralParams.PeriodMilliSecsTaskLogic );
// cyclic thread for real-time is in the kernel (RTLinux/RTAI case, not for Xenomai easily also done here!)
#ifndef RT_SUPPORT
		if ( CreateTask( ID_TASK_SCAN_INPUTS, "CL_INPUTS", 98/*Priority*/, 16/*StackSizeInKo*/, InfosGene->GeneralParams.PeriodMilliSecsTaskScanInputs*1000, ( void * (*)(void *) )CyclicCalcRefreshOfScanInputsTask )
			&& CreateTask( ID_TASK_LOGIC, "CL_LOGIC", 99/*Priority*/, 16/*StackSizeInKo*/, InfosGene->GeneralParams.PeriodMilliSecsTaskLogic*1000, ( void * (*)(void *) )CyclicCalcRefreshOfLogicTask ) )
#endif
		{
			char WaitHere = TRUE;
			char ProjectLoadedOk;

#ifdef GTK_INTERFACE
			if ( nogui==0 )
			{
				InitGtkWindows( argc, argv );
#ifndef __WIN32__
				if ( Preferences.UseSysLog )
					syslog( LOG_USER|LOG_DEBUG, "Init Gtk Windows Done\n" );
#endif
			}
#endif

			if ( TheProject[ 0 ]!='\0' )
			{
				//TODO: perhaps pass here TheProject as parameter, and is copied internally
				//into InfosGene->CurrentProjectFileName in the function LoadProjectFiles() ?!
				//lot of works to do in the gtk part for LoadProjectFiles & SaveProjectFiles...
				ProjectLoadedOk = LoadProjectFiles( InfosGene->CurrentProjectFileName );
#ifdef GTK_INTERFACE
				if ( nogui==0 )
				{
					UpdateAllGtkWindows( );
					MessageInStatusBar( ProjectLoadedOk?"Project loaded and running":"Project failed to load...");
					SetGtkMenuStateForRunStopSwitch( ProjectLoadedOk );
				}
#endif

#ifndef RT_SUPPORT
#ifdef GTK_INTERFACE
				if ( !GeneralParamsMirror.RealInputsOutputsOnlyOnTarget )
#endif
				{
					OpenHardware( 0 );
//					ConfigHardware( );
					InfosGene->AskToConfHardInputs = TRUE;
					InfosGene->AskToConfHardOutputs = TRUE;
				}
#endif
			}
			InfosGene->LadderState = ProjectLoadedOk?STATE_RUN:STATE_STOP;

#ifdef GTK_INTERFACE
			//ProblemWithPrint		gdk_threads_enter( );
			if ( nogui==0 )
			{
				UpdateWindowTitleWithProjectName( );
				gtk_main();
gdk_threads_leave();
				WaitHere = FALSE;
			}
			//ProblemWithPrint		gdk_threads_leave( );
#endif
			if ( WaitHere )
			{
				debug_printf("Press CTRL-C or send 'kill' signal to end...!\n");
//				for( ;; )
				while( !QuitClassicLadder )
					DoPauseMilliSecs( 100 );
				debug_printf("Go out of wait main...\n");
			}

		}
	}
	else
	{
#ifndef __WIN32__
		if ( Preferences.UseSysLog )
			syslog( LOG_USER|LOG_DEBUG, "Failed to alloc memory required !!!\n" );
#endif
		ClassicLadder_FreeAll( TRUE/*CleanAndRemoveTmpDir*/ );
	}

#ifndef __WIN32__
	if ( Preferences.UseSysLog )
		syslog( LOG_USER|LOG_DEBUG, "End of main (should be never reached!!!!)\n" );
#endif
	return 0;
}
#endif
